package org.example.service.impl;

import org.example.dto.CreateShortUrlRequest;
import org.example.dto.CreateShortUrlResponse;
import org.example.entity.ShortUrl;
import org.example.mapper.ShortUrlMapper;
import org.example.service.ShortUrlService;
import org.example.service.AccessStatisticsService;
import org.example.util.ShortUrlGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 短链接服务实现类
 * 实现短链接的创建、查询、重定向等核心业务逻辑
 * 使用Redis缓存提高性能，使用事务保证数据一致性
 * 
 * @author Your Name
 * @since 2024-07-01
 */
@Service  // 标识这是一个Spring服务类，会被自动注册到Spring容器
public class ShortUrlServiceImpl implements ShortUrlService {
    
    // 注入短链接数据访问层，用于数据库操作
    @Autowired
    private ShortUrlMapper shortUrlMapper;
    
    // 注入访问统计服务，用于记录访问日志
    @Autowired
    private AccessStatisticsService accessStatisticsService;
    
    // 注入Redis操作模板，用于缓存操作
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    // 从配置文件读取服务器端口，默认8080
    // 用于构建完整的短链接URL
    @Value("${server.port:8080}")
    private String serverPort;
    
    // 从配置文件读取应用上下文路径，默认为空
    // 用于构建完整的短链接URL
    @Value("${server.servlet.context-path:}")
    private String contextPath;
    
    // ==================== Redis配置常量 ====================
    
    // Redis key前缀，用于区分不同类型的缓存数据
    private static final String REDIS_SHORT_URL_PREFIX = "short_url:";
    private static final String REDIS_ORIGINAL_URL_PREFIX = "original_url:";
    // Redis缓存过期时间，设置为7天
    private static final int REDIS_EXPIRE_DAYS = 7;
    
    // ==================== 核心业务方法 ====================
    
    /**
     * 创建短链接
     * 
     * 业务逻辑：
     * 1. 检查是否已存在相同的原始链接，避免重复创建
     * 2. 生成或验证短链接编码（支持自定义编码）
     * 3. 计算过期时间（可选）
     * 4. 保存到数据库
     * 5. 缓存到Redis提高性能
     * 
     * @param request 创建短链接请求对象
     * @return 创建结果响应
     */
    @Override
    @Transactional  // 开启事务，确保数据一致性
    public CreateShortUrlResponse createShortUrl(CreateShortUrlRequest request) {
        try {
            // 检查是否已存在相同的原始链接
            // 如果存在，直接返回已存在的短链接信息，避免重复创建
            ShortUrl existingUrl = shortUrlMapper.selectByOriginalUrl(request.getOriginalUrl());
            if (existingUrl != null) {
                return new CreateShortUrlResponse(true, existingUrl.getShortCode(), 
                    buildShortUrl(existingUrl.getShortCode()), existingUrl.getOriginalUrl(), 
                    existingUrl.getExpireTime());
            }
            
            // 生成短链接编码
            String shortCode;
            if (request.getCustomCode() != null && !request.getCustomCode().trim().isEmpty()) {
                // 用户指定了自定义编码
                // 验证自定义编码格式是否正确（8位数字、字母组合）
                if (!ShortUrlGenerator.isCustomCodeAvailable(request.getCustomCode())) {
                    return new CreateShortUrlResponse(false, "自定义编码格式不正确，必须为8位数字、字母组合");
                }
                
                // 检查自定义编码是否已存在，避免冲突
                ShortUrl existingCustom = shortUrlMapper.selectByShortCode(request.getCustomCode());
                if (existingCustom != null) {
                    return new CreateShortUrlResponse(false, "自定义编码已存在，请选择其他编码");
                }
                
                // 使用用户指定的自定义编码
                shortCode = request.getCustomCode();
            } else {
                // 系统自动生成编码，使用ShortUrlGenerator工具类
                // 添加重试机制，处理可能的编码冲突
                shortCode = generateUniqueShortCodeWithRetry();
            }
            
            // 计算过期时间
            LocalDateTime expireTime = null;
            if (request.getExpireDays() != null && request.getExpireDays() > 0) {
                // 从当前时间开始，加上指定的天数
                expireTime = LocalDateTime.now().plusDays(request.getExpireDays());
            }
            
            // 创建短链接实体对象，使用建造者模式
            ShortUrl shortUrl = ShortUrl.builder()
                .shortCode(shortCode)           // 设置短链接编码
                .originalUrl(request.getOriginalUrl())  // 设置原始链接
                .accessCount(0L)               // 初始访问次数为0
                .status(1)                     // 状态设为启用（1-启用，0-禁用）
                .expireTime(expireTime)        // 设置过期时间，null表示永不过期
                .build();
            
            // 保存到数据库，使用MyBatis Mapper
            shortUrlMapper.insert(shortUrl);
            
            // 缓存到Redis，提高后续查询性能
            // 设置7天过期时间，平衡性能和存储空间
            cacheToRedis(shortCode, request.getOriginalUrl());
            
            // 返回成功响应，包含完整的短链接信息
            return new CreateShortUrlResponse(true, shortCode, 
                buildShortUrl(shortCode), request.getOriginalUrl(), expireTime);
                
        } catch (Exception e) {
            // 如果出现异常，返回失败响应
            // 记录异常信息，便于问题排查
            return new CreateShortUrlResponse(false, "创建短链接失败：" + e.getMessage());
        }
    }
    
    /**
     * 根据短链接编码获取原始链接
     * 
     * 查询策略：
     * 1. 优先从Redis缓存获取，提高查询性能
     * 2. 缓存未命中时，从数据库查询
     * 3. 查询成功后，将结果缓存到Redis
     * 4. 检查短链接状态和过期时间
     * 
     * @param shortCode 短链接编码
     * @return 原始链接，如果不存在或已禁用返回null
     */
    @Override
    public String getOriginalUrl(String shortCode) {
        // 先从Redis缓存获取，提高性能
        // 使用REDIS_ORIGINAL_URL_PREFIX前缀构建key
        String originalUrl = redisTemplate.opsForValue().get(REDIS_ORIGINAL_URL_PREFIX + shortCode);
        if (originalUrl != null) {
            return originalUrl;  // 缓存命中，直接返回
        }
        
        // 如果Redis中没有，从数据库获取
        ShortUrl shortUrl = shortUrlMapper.selectByShortCode(shortCode);
        if (shortUrl != null && shortUrl.getStatus() == 1) {
            // 检查是否过期
            // 如果设置了过期时间且已过期，返回null
            if (shortUrl.getExpireTime() != null && shortUrl.getExpireTime().isBefore(LocalDateTime.now())) {
                return null;  // 已过期
            }
            
            // 缓存到Redis，下次查询时可以直接从缓存获取
            // 提高后续查询性能，减少数据库压力
            cacheToRedis(shortCode, shortUrl.getOriginalUrl());
            return shortUrl.getOriginalUrl();
        }
        
        return null;  // 短链接不存在或已禁用
    }
    
    /**
     * 重定向到原始链接
     * 
     * 功能说明：
     * 1. 获取原始链接用于重定向
     * 2. 异步记录访问统计信息（IP、User-Agent、Referer等）
     * 3. 更新访问次数计数器
     * 4. 返回原始链接用于重定向
     * 
     * 注意：访问统计记录是异步的，不会阻塞重定向流程
     * 
     * @param shortCode 短链接编码
     * @param ipAddress 访问者IP地址
     * @param userAgent 用户代理信息
     * @param referer 来源页面
     * @return 原始链接，如果不存在返回null
     */
    @Override
    public String redirectToOriginalUrl(String shortCode, String ipAddress, String userAgent, String referer) {
        // 获取原始链接
        String originalUrl = getOriginalUrl(shortCode);
        if (originalUrl != null) {
            // 异步记录访问统计，不阻塞主流程
            // 记录访问者的详细信息，用于数据分析和防刷机制
            accessStatisticsService.recordAccess(shortCode, ipAddress, userAgent, referer);
            
            // 更新访问次数
            // 在数据库中将访问次数+1
            shortUrlMapper.updateAccessCount(shortCode);
            
            return originalUrl;
        }
        return null;
    }
    
    /**
     * 获取短链接详细信息
     * 
     * @param shortCode 短链接编码
     * @return 短链接实体对象，包含所有字段信息
     */
    @Override
    public ShortUrl getShortUrlInfo(String shortCode) {
        // 直接从数据库查询短链接信息
        // 这个方法通常用于管理后台，不需要缓存
        return shortUrlMapper.selectByShortCode(shortCode);
    }
    
    /**
     * 获取所有短链接
     * 
     * 注意：这个方法会返回所有短链接，数据量大时可能影响性能
     * 建议使用分页查询替代
     * 
     * @return 所有短链接的列表
     */
    @Override
    public List<ShortUrl> getAllShortUrls() {
        // 查询所有短链接
        // 适用于数据量较小的场景
        return shortUrlMapper.selectAll();
    }
    
    /**
     * 分页查询短链接
     * 
     * 分页逻辑：
     * 1. 计算偏移量：offset = (page - 1) * size
     * 2. 使用LIMIT语句进行分页查询
     * 3. 适用于大数据量的分页展示
     * 
     * @param page 页码，从1开始
     * @param size 每页大小
     * @return 当前页的短链接列表
     */
    @Override
    public List<ShortUrl> getShortUrlsByPage(int page, int size) {
        // 计算分页偏移量
        // 例如：第1页，每页10条，offset = (1-1) * 10 = 0
        int offset = (page - 1) * size;
        // 分页查询短链接
        return shortUrlMapper.selectByPage(offset, size);
    }
    
    /**
     * 更新短链接状态
     * 
     * 状态说明：
     * - 1：启用，短链接可以正常访问
     * - 0：禁用，短链接无法访问
     * 
     * 缓存策略：
     * - 启用时：保持缓存不变
     * - 禁用时：删除Redis缓存，确保无法访问
     * 
     * @param shortCode 短链接编码
     * @param status 新状态（1-启用，0-禁用）
     * @return 更新是否成功
     */
    @Override
    @Transactional  // 开启事务，确保数据一致性
    public boolean updateShortUrlStatus(String shortCode, Integer status) {
        // 更新数据库中的状态
        int result = shortUrlMapper.updateStatus(shortCode, status);
        if (result > 0) {
            // 如果禁用，从Redis中删除缓存
            // 确保禁用的短链接无法通过缓存访问
            if (status == 0) {
                redisTemplate.delete(REDIS_SHORT_URL_PREFIX + shortCode);
                redisTemplate.delete(REDIS_ORIGINAL_URL_PREFIX + shortCode);
            }
            return true;
        }
        return false;
    }
    
    /**
     * 删除短链接
     * 
     * 删除策略：
     * 1. 从数据库物理删除短链接记录
     * 2. 从Redis中删除相关缓存
     * 3. 使用事务确保数据一致性
     * 
     * @param shortCode 短链接编码
     * @return 删除是否成功
     */
    @Override
    @Transactional  // 开启事务，确保数据一致性
    public boolean deleteShortUrl(String shortCode) {
        // 从数据库删除
        int result = shortUrlMapper.deleteByShortCode(shortCode);
        if (result > 0) {
            // 从Redis中删除缓存
            // 确保删除后无法通过缓存访问
            redisTemplate.delete(REDIS_SHORT_URL_PREFIX + shortCode);
            redisTemplate.delete(REDIS_ORIGINAL_URL_PREFIX + shortCode);
            return true;
        }
        return false;
    }
    
    /**
     * 获取短链接访问次数
     * 
     * @param shortCode 短链接编码
     * @return 访问次数，如果短链接不存在返回0
     */
    @Override
    public Long getAccessCount(String shortCode) {
        // 查询短链接信息，获取访问次数
        ShortUrl shortUrl = shortUrlMapper.selectByShortCode(shortCode);
        return shortUrl != null ? shortUrl.getAccessCount() : 0L;
    }
    
    // ==================== 私有辅助方法 ====================
    
    /**
     * 构建完整的短链接URL
     * 
     * @param shortCode 短链接编码
     * @return 完整的短链接URL
     */
    private String buildShortUrl(String shortCode) {
        // 构建基础URL
        String baseUrl = "http://localhost:" + serverPort;
        // 如果有上下文路径，添加到URL中
        if (contextPath != null && !contextPath.isEmpty()) {
            baseUrl += contextPath;
        }
        // 拼接短链接编码
        return baseUrl + "/" + shortCode;
    }
    
    /**
     * 生成不重复的短链接编码（带重试机制）
     * 
     * 重试策略：
     * 1. 首先尝试使用时间戳+随机数生成
     * 2. 如果冲突，使用递增ID生成
     * 3. 最多重试5次，避免无限循环
     * 
     * @return 不重复的短链接编码
     */
    private String generateUniqueShortCodeWithRetry() {
        int maxRetries = 5;
        int retryCount = 0;
        
        while (retryCount < maxRetries) {
            try {
                String shortCode;
                
                if (retryCount == 0) {
                    // 第一次尝试：使用时间戳+随机数生成，冲突概率最低
                    shortCode = ShortUrlGenerator.generateUniqueShortCode();
                } else {
                    // 后续重试：使用递增ID生成
                    shortCode = ShortUrlGenerator.generateShortCode();
                }
                
                // 检查生成的编码是否已存在
                ShortUrl existing = shortUrlMapper.selectByShortCode(shortCode);
                if (existing == null) {
                    // 编码不重复，返回成功
                    return shortCode;
                }
                
                // 编码重复，继续重试
                retryCount++;
                
            } catch (Exception e) {
                // 如果出现异常，使用递增ID生成
                retryCount++;
                if (retryCount >= maxRetries) {
                    // 达到最大重试次数，使用递增ID作为最后的备选方案
                    return ShortUrlGenerator.generateShortCode();
                }
            }
        }
        
        // 如果所有重试都失败，使用递增ID作为最后的备选方案
        return ShortUrlGenerator.generateShortCode();
    }
    
    /**
     * 缓存到Redis
     * 使用双Key策略，提高查询灵活性
     * 
     * @param shortCode 短链接编码
     * @param originalUrl 原始链接
     */
    private void cacheToRedis(String shortCode, String originalUrl) {
        // 构建Redis key
        String shortUrlKey = REDIS_SHORT_URL_PREFIX + shortCode;
        String originalUrlKey = REDIS_ORIGINAL_URL_PREFIX + shortCode;
        
        // 设置缓存，7天后自动过期
        redisTemplate.opsForValue().set(shortUrlKey, originalUrl, REDIS_EXPIRE_DAYS, TimeUnit.DAYS);
        redisTemplate.opsForValue().set(originalUrlKey, originalUrl, REDIS_EXPIRE_DAYS, TimeUnit.DAYS);
    }
}
